
package it.csttech.date;

import java.io.IOException;
import java.io.InputStream;
import java.lang.annotation.Annotation;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.Date;

import javax.servlet.http.HttpServletRequest;
import javax.ws.rs.Consumes;
import javax.ws.rs.WebApplicationException;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.MultivaluedMap;
import javax.ws.rs.ext.MessageBodyReader;
import javax.ws.rs.ext.Provider;

import org.glassfish.jersey.internal.util.ReflectionHelper;

import com.fasterxml.jackson.core.Version;
import com.fasterxml.jackson.databind.JavaType;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.module.SimpleModule;

/**
 * OVERVIEW This class is meant to unmarshal json objects into java objects
 * 
 * @author marcello.felappi
 * @author marco.dalena
 */
@Provider
@Consumes(MediaType.APPLICATION_JSON)
public class ObjectBodyReader implements MessageBodyReader<Object> {

	/**
	 * Context request
	 */
	@Context
	HttpServletRequest request;

	/**
	 * This should method should check if object sent to the reader can be read,
	 * returning true; otherwise it returns false. Since every object pass
	 * through this reader, the method always returna true.
	 * 
	 * @param Class
	 *            cl - class of the object to be read
	 * @param Type
	 *            type - type of the object to be read
	 * @param Annotation
	 *            ann - annotations on the object
	 * @param MediaType
	 *            media - media type of the object
	 * @return boolean - always true
	 */
	@Override
	public boolean isReadable(
		Class<?> cl, Type type, Annotation[] ann, MediaType media) {

		return true;
	}

	/**
	 * This method reads the JSON object from the input stream and translates it
	 * in the java object of class cl and Type type
	 * 
	 * @param Class
	 *            cl - class of the object to be read
	 * @param Type
	 *            type - type of the object to be read
	 * @param Annotation
	 *            ann - annotations on the object
	 * @param MediaType
	 *            media - media type of the object
	 * @param MultivaluedMap
	 *            <String, String> multiMap - contains the header of the request
	 * @param InputStream
	 *            input - input stream from which reading the data
	 * @return Object - the deserialized object read
	 */
	@Override
	public Object readFrom(
		Class<Object> cl, Type type, Annotation[] ann, MediaType media,
		MultivaluedMap<String, String> multiMap, InputStream input)
		throws IOException, WebApplicationException {

		ObjectMapper mapper;

		mapper = new ObjectMapper();

		DateJsonDeserializer datedeser =
			new DateJsonDeserializer(request.getLocale());
		SimpleModule simpleDate =
			new SimpleModule(
				"dateModule", new Version(0, 1, 0, "alpha", "", ""));
		simpleDate.addDeserializer(Date.class, datedeser);
		mapper.registerModule(simpleDate);

		FloatJsonDeserializer flreader =
			new FloatJsonDeserializer(request.getLocale());
		SimpleModule simple =
			new SimpleModule("floatModule", new Version(
				0, 1, 0, "alpha", "", ""));
		simple.addDeserializer(Float.class, flreader);
		mapper.registerModule(simple);
		return mapper.readValue(input, getJavaType(mapper, cl, type));

	}

	/**
	 * This method return the java type of the object to be deserialized. It
	 * search recursively to allow usage of nested Collections and Generics.
	 * 
	 * @param ObjectMapper
	 *            mapper - object mapper in use
	 * @param Class
	 *            cl - class to be read
	 * @param type
	 *            type - type to be read
	 * @return JavaType of the object
	 */
	public JavaType getJavaType(ObjectMapper mapper, Class<?> cl, Type type) {

		if (!(ReflectionHelper.isSubClassOf(
			type.getClass(), ParameterizedType.class)))
			return mapper.getTypeFactory().constructType(type);
		else {
			ParameterizedType par = (ParameterizedType) type;
			Type[] erasure = par.getActualTypeArguments();
			JavaType[] classes = new JavaType[erasure.length];
			for (int i = 0; i < erasure.length; i++) {
				if (!(ReflectionHelper.isSubClassOf(
					erasure[i].getClass(), ParameterizedType.class)))

					classes[i] =
						mapper.getTypeFactory().constructType((erasure[i]));

				else {

					classes[i] =
						getJavaType(
							mapper,
							(Class<?>) (((ParameterizedType) erasure[i]).getRawType()),
							(ParameterizedType) erasure[i]);

				}
			}

			return mapper.getTypeFactory().constructParametricType(cl, classes);

		}
	}

}
